﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Xml;

namespace blackhouse.Payments {
    public class RemoteUpdatePaymentTransaction {

        #region Fields

        private IPaymentTransaction storedTransaction;
        private PayUConfig config;
        private delegate void UpdateDelegate();
        private XmlDocument xml;

        #endregion

        #region Constructors

        public RemoteUpdatePaymentTransaction(IPaymentTransaction transaction) {
            this.storedTransaction = transaction;
            this.config = this.storedTransaction.Configuration;
        }

        #endregion

        #region Methods

        public void UpdateFromService() {
            this.Update();
        }

        public void UpdateFromServiceAsync() {
            UpdateDelegate ud = new UpdateDelegate(Update);
            ud.BeginInvoke(new AsyncCallback(UpdateComplete), null);
        }

        private void Update() {
            string sig = this.GetSign();
            NameValueCollection requestData = this.GetRequestData(sig);
            xml = this.GetRemoteTransaction(this.config.ProcedureGetUrl, requestData);

            if (!this.CheckSign(xml))
                throw new Exception("Wiadomość została błędnie podpisana przez platnosci.pl");
            IPaymentTransaction copyTransaction = new PaymentTransaction(this.storedTransaction.PaymentTrancactionId); // prevTransaction
            this.ReadPaymentTransactionToInstance(this.storedTransaction, xml);
            if (copyTransaction.ChargeValue != this.storedTransaction.ChargeValue)
                throw new Exception(string.Format("Założona transakcja miała wartość {0:F2}, a w systemie płatności ma wartość {1:F2}", this.storedTransaction.ChargeValue, copyTransaction.ChargeValue));
            this.storedTransaction.Save();
            this.storedTransaction.SaveUpdateSnapshot(this.GetResponseSnapshot());
            this.NotifyOfUpdate(copyTransaction, this.storedTransaction);
        }

        private void NotifyOfUpdate(IPaymentTransaction prevTransaction, IPaymentTransaction currentTransaction) {
            IPaymentTransactionExtension ext = config.GetExtension();
            if (ext != null)
                ext.NotifyUpdate(prevTransaction, currentTransaction);
        }

        private string GetResponseSnapshot() {
            if (xml != null)
                return xml.OuterXml;
            else
                return "";
        }

        private bool CheckSign(XmlDocument document) {
            XmlElement xmlTrans = document["response"]["trans"];
            string s = xmlTrans["pos_id"].InnerText + xmlTrans["session_id"].InnerText + xmlTrans["order_id"].InnerText + xmlTrans["status"].InnerText + xmlTrans["amount"].InnerText + xmlTrans["desc"].InnerText + xmlTrans["ts"].InnerText + this.config.KeyIn;
            return xmlTrans["sig"].InnerText.Equals(s.GetMd5().ReplaceEx("-", ""), StringComparison.InvariantCultureIgnoreCase);

        }

        private NameValueCollection GetRequestData(string sig) {
            NameValueCollection postFields = new NameValueCollection(4);
            postFields.Add("pos_id", this.config.PosID.ToString());
            postFields.Add("session_id", this.storedTransaction.UserSessionId);
            postFields.Add("ts", this.storedTransaction.Ts);
            postFields.Add("sig", sig);
            return postFields;
        }

        private void ReadPaymentTransactionToInstance(IPaymentTransaction setInstance, XmlDocument doc) {
            if (doc["response"]["status"].InnerText == "OK") {
                XmlElement xmlTrans = doc["response"]["trans"];

                int statusId = Int32.Parse(xmlTrans["status"].InnerText);
                PaymentTransactionStatus status = new PaymentTransactionStatus(statusId);
                if (!status.Exist)
                    throw new Exception("Nieznany status: " + statusId);
                setInstance.Status = status;
                string paymentTypeKey = xmlTrans["pay_type"].InnerText;
                if (!string.IsNullOrEmpty(paymentTypeKey)) {
                    PaymentType type = new PaymentType(paymentTypeKey);
                    if (!type.Exist)
                        throw new Exception("Nieznany typ płatności: " + paymentTypeKey);
                    setInstance.PaymentType = type;
                }
                setInstance.ChargeValue = Math.Round(Convert.ToDouble(Int32.Parse(xmlTrans["amount"].InnerText) / 100d), 2);
                setInstance.CreatedDate = this.GetXmlDatetime(xmlTrans["create"].InnerText);
                setInstance.BeginDate = this.GetXmlDatetime(xmlTrans["init"].InnerText);
                setInstance.SentDate = this.GetXmlDatetime(xmlTrans["sent"].InnerText);
                setInstance.ReceiveDate = this.GetXmlDatetime(xmlTrans["recv"].InnerText);
                setInstance.CancelDate = this.GetXmlDatetime(xmlTrans["cancel"].InnerText);

            }
        }

        private DateTime GetXmlDatetime(string val) {
            DateTime v = DateTime.MinValue;
            DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", CultureInfo.GetCultureInfo("pl-PL"), DateTimeStyles.None, out v);
            return v;
        }

        private string GetSign() {
            string s = this.storedTransaction.Configuration.PosID + this.storedTransaction.UserSessionId + this.storedTransaction.Ts + this.config.KeyOut;
            return s.GetMd5().ToLower().ReplaceEx("-", "");
        }

        private XmlDocument GetRemoteTransaction(string url, NameValueCollection requestData) {
            byte[] data;
            using (WebClient client = new WebClient()) {
                client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
                client.Encoding = Encoding.UTF8;
                data = client.UploadValues(url, "POST", requestData);
            }
            string dataS = Encoding.UTF8.GetString(data);
            XmlDocument document = new XmlDocument();
            document.LoadXml(dataS);
            return document;
        }

        private void UpdateComplete(IAsyncResult result) {
            this.storedTransaction.AddLog();
        }

        #endregion

    }
}